package name.huzhenbo.java.algorithm.talent;

import name.huzhenbo.java.collection.Queue;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Collections;

/**
 * Four queues in the bank
 * The service time of each customer does not exceed 30 miniutes
 * The interval of the arrival of each customer does not exceed 5 miniutes
 * <p/>
 * Simulate and calculate the average duration of each customer: departure time - arrive time
 */
class QueuesInBank {
    private static final int BUSINESS_PERIOD = 8 * 60;
    private static final int QUEUE_NUM = 4;

    private final List<Event> events;
    private final Queue<Event>[] queues;
    private int totalTime;
    private int totalSrvicePeriod;
    private int customerNum;

    public QueuesInBank() {
        events = new ArrayList<Event>();
        queues = new Queue[QUEUE_NUM];
        for (int i = 0; i < queues.length; i++) {
            queues[i] = new Queue<Event>();
        }
    }

    public double averageDuration() {
        new Customer(0);
        while (!events.isEmpty()) {
            events.remove(0).happen();
        }

        System.out.println("Total time: " + totalTime);
        System.out.println("Total service time: " + totalSrvicePeriod);
        System.out.println("Number of customer: " + customerNum);

        return new Double(totalTime) / new Double(customerNum);
    }


    private Queue minQueue() {
        int min = 0;
        for (int i = 0; i < QUEUE_NUM; i++) {
            if (queues[i].size() < queues[min].size()) {
                min = i;
            }
        }
        return queues[min];
    }

    private class Customer {
        private static final int MAX_INTERVAL = 4;
        private static final int MAX_SERVICE = 20;

        private int servicePeriod;
        private int arrivalTime;

        public Customer(int currentTime) {
            this.arrivalTime = currentTime + new Random().nextInt(MAX_INTERVAL) + 1;
            this.servicePeriod = new Random().nextInt(MAX_SERVICE) + 1;
            sortedInsertEvent(new ArrivalEvent(this, arrivalTime));
            customerNum++;
            totalSrvicePeriod += servicePeriod;
        }

    }

    private void sortedInsertEvent(Event event){
        events.add(Math.abs(Collections.binarySearch(events, event))  - 1, event);
    }

    private abstract class Event implements Comparable<Event> {
        protected int occurTime;

        public Event(int occurTime) {
            this.occurTime = occurTime;
        }

        public int compareTo(Event another) {
            return occurTime < another.occurTime ? -1 : 1;
        }

        public abstract void happen();
    }

    private class ArrivalEvent extends Event {
        protected Customer customer;

        public ArrivalEvent(Customer object, int occurTime) {
            super(occurTime);
            this.customer = object;
        }

        @Override
        public void happen() {
            if (occurTime < BUSINESS_PERIOD) {
                new Customer(occurTime);
            }

            Queue toInsert = minQueue();
            toInsert.en(customer);

            if (toInsert.size() == 1) {
                sortedInsertEvent(new DepartureEvent(occurTime + customer.servicePeriod, toInsert));
            }
        }
    }

    private class DepartureEvent extends Event {
        private Queue queue;

        public DepartureEvent(int occurTime, Queue queue) {
            super(occurTime);
            this.queue = queue;
        }

        @Override
        public void happen() {
            Customer current = (Customer) queue.de();
            totalTime += occurTime - current.arrivalTime;

            if (queue.size() > 0) {
                sortedInsertEvent(new DepartureEvent(occurTime + ((Customer) queue.first()).servicePeriod, queue));
            }
        }
    }
}
